package com.dbappsecurity.network;

import com.dbappsecurity.core.utils.RxUtils;
import com.dbappsecurity.network.api.ApiHelper;
import com.dbappsecurity.network.api.services.ApiService;
import com.google.gson.Gson;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import io.reactivex.Scheduler;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;

/**
 * Created by Arirus on 2018/4/2.
 */

public class Api {

  @Retention(RetentionPolicy.SOURCE)
  public @interface Method {
    String GET ="GET";
    String POST = "POST";
    String PUT = "PUT";
    String DELETE = "DELETE";
  }


  public static final String ARIRUS = "ARIRUS_Api";

  public interface onResponse extends Consumer<String> {
  }

  public interface onResponseObject<T> extends Consumer<T> {
  }

  public interface onResponseArray<T> extends Consumer<List<T>> {
  }

  public interface onError extends Consumer<Throwable> {

  }

  public interface onFinish extends Action {

  }

  public interface onBeforeRequest extends Consumer<Disposable> {
  }

  public interface onAfterRequest extends Action {
  }

  Thunder mThunder;
  Gson mGson;
  ApiService mService;

  private Single<String> api;

  private @Method String mMethod = Method.GET;
  private String mUrl;
  private List<String> mPaths;
  private Map<String, Object> mQuerys;
  private Map<String, Object> mFields;
  private Map<String, RequestBody> mParts;
  private RequestBody mBody;
  private MultipartBody.Part mPart;

  private onBeforeRequest mBefore;
  private Scheduler mBeforeScheduler;
  private onAfterRequest mAfter;
  private Scheduler mAfterScheduler;

  Api(Thunder client, Gson gson) {
    mThunder = client;
    mGson = gson;
    mPaths = new ArrayList<>();
    mQuerys = new HashMap<>();
    mFields = new HashMap<>();
    mParts = new HashMap<>();
    mService = mThunder.mRetrofit.create(ApiService.class);
    mBeforeScheduler = AndroidSchedulers.mainThread();
    mAfterScheduler = AndroidSchedulers.mainThread();
  }

  public Api method(@Method String method) {
    mMethod = method;
    return this;
  }

  public Api url(String url) {
    mUrl = url;
    return this;
  }

  public Api path(String path) {
    mPaths.add(path);
    return this;
  }

  public Api query(String key, Object query) {
    mQuerys.put(key, query);
    return this;
  }

  public Api field(String key, Object filed) {
    mFields.put(key, filed);
    return this;
  }

  public Api part(String key, RequestBody body) {
    mParts.put(key, body);
    return this;
  }

  public <T> Api body(T body) {
    mBody = RequestBody.create(ApiHelper.JsonMediaType(), mGson.toJson(body));
    return this;
  }

  public Api body(String jsonStr) {
    mBody = RequestBody.create(ApiHelper.JsonMediaType(), jsonStr);
    return this;
  }

  public Api part(MultipartBody.Part pa) {
    mPart = pa;
    return this;
  }

  public Api before(onBeforeRequest beforeRequest) {
    mBefore = (beforeRequest);
    return this;
  }

  public Api before(onBeforeRequest beforeRequest, Scheduler beforeScheduler) {
    mBefore = (beforeRequest);
    mBeforeScheduler = beforeScheduler;
    return this;
  }

  public Api after(onAfterRequest afterRequest) {
    mAfter = (afterRequest);
    return this;
  }

  public Api after(onAfterRequest afterRequest, Scheduler afterScheduler) {
    mAfter = (afterRequest);
    mAfterScheduler = afterScheduler;
    return this;
  }

  private Single<String> requester() {
    if (api != null) return api;
    String url = ApiHelper.rawUrl2Url(mUrl, mPaths);
    switch (mMethod) {
      case Method.GET:
        if (mQuerys.size() == 0) {
          api = mService.get(url);
        } else {
          api = mService.get(url, mQuerys);
        }
        break;
      case Method.POST:
        if (mFields.size() != 0) {
          api = mService.post(url, mFields);
        } else if (mBody != null) {
          api = mService.post(url, mBody);
        } else if (mParts.size() != 0) {
          api = mService.postPartMap(url, mParts);
        } else {
          api = mService.postPart(url, mPart);
        }
        break;
      case Method.PUT:
        if (mBody != null) api = mService.put(url, mBody);
        break;
    }

    return api;
  }

  public Single<String> observable() {
    return requester().compose(RxUtils.rxSingleSchedulerHelper())
        .compose(stringObservable -> mBefore == null ? stringObservable
            : stringObservable.doOnSubscribe(mBefore).subscribeOn(mBeforeScheduler))
        .compose(stringObservable -> mAfter == null ? stringObservable
            : stringObservable.doOnDispose(mAfter).unsubscribeOn(mAfterScheduler));
  }

  public <T> Single<T> observable(Class<T> responseType) {
    return requester().compose(RxUtils.rxSingleSchedulerHelper())
        .compose(stringObservable -> mBefore == null ? stringObservable
            : stringObservable.doOnSubscribe(mBefore).subscribeOn(mBeforeScheduler))
        .compose(stringObservable -> mAfter == null ? stringObservable
            : stringObservable.doOnDispose(mAfter).unsubscribeOn(mAfterScheduler))
        .map(string -> mGson.fromJson(string, responseType));
  }

  public <T> Single<List<T>> observableList(Class<T> responseType) {
    return requester().compose(RxUtils.rxSingleSchedulerHelper())
        .compose(stringObservable -> mBefore == null ? stringObservable
            : stringObservable.doOnSubscribe(mBefore).subscribeOn(mBeforeScheduler))
        .compose(stringObservable -> mAfter == null ? stringObservable
            : stringObservable.doOnDispose(mAfter).unsubscribeOn(mAfterScheduler))
        .map(string -> (mGson.fromJson(string,
            $Gson$Types.newParameterizedTypeWithOwner(null, List.class, responseType))));
  }

  public Disposable request(final onResponse onResponse) {
    return request(onResponse, new ApiHelper.DefaultErrorHandler());
  }

  public Disposable request(final onResponse onResponse, final onError onError) {

    return observable().subscribe(onResponse, onError::accept);
  }

  public <T> Disposable request(final Type responseType, final onResponseArray<T> onResponse) {
    return request(responseType, onResponse, new ApiHelper.DefaultErrorHandler());
  }

  public <T> Disposable request(final Type responseType, final onResponseArray<T> onResponse,
      final onError onError) {
    return observable().map(s -> {
      T[] array = mGson.fromJson(s, TypeToken.getArray(responseType).getType());
      return Arrays.asList(array);
    }).subscribe(onResponse, onError);
  }

  public <T> Disposable request(Type responseType, final onResponseObject<T> onResponse) {
    return request(responseType, onResponse, new ApiHelper.DefaultErrorHandler());
  }

  public <T> Disposable request(Type responseType, final onResponseObject<T> onResponse,
      final onError onError) {
    return observable().map((Function<String, T>) s -> mGson.fromJson(s, responseType))
        .subscribe(onResponse, onError);
  }
}
